﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using System.Xml;
using System.IO;
using System.Reflection;

namespace DARE
{
    /// <summary>
    /// Manages the Head-Up Display
    /// This class is the first class that must be created to create a HUD.
    /// We can load an xml file that describes the hud with Load method.
    /// </summary>
    /// <example>
    /// <code>
    /// HUD.IsCursorVisible = true;
    /// HUD.Show();
    /// </code>
    /// <code>
    /// HUD.Add(new CWidget(HUD, null, "Widget"));
    /// HUD.Show();
    /// </code>
    /// </example>
    [AttrTypeDependentSerializable(typeof(DARE.Save.DataStructures.CHUDData))]
    public sealed class CHUDSystem : ARenderable
    {
        #region fields

        private CCursor m_cursor;
        private SpriteBatch m_spriteBatch;

        private CWidget m_widgetLeftClicked;
        private bool m_mouseLeftPressed;
        private CWidget m_widgetRightClicked;
        private bool m_mouseRightPressed;
        private CWidget m_widgetMiddleClicked;
        private bool m_mouseMiddlePressed;
        private CScene m_scene;
        private bool m_isVisible = false;
        private bool m_isCursorVisible = true;

        private CSkinner m_skinner = null;
        private CHUDShader m_shader;

        private CMouse.EButton m_pressedButton;

        #endregion

        #region properties

        public Game Game { get; set; }

        /// <summary>
        /// gets/sets the entire widget list contained by this HUDSystem.
        /// In this list, there is every widget, active or inactive, even the children of every other widgets.
        /// </summary>
        public Dictionary<string, CWidget> Widgets { get; set; }

        /// <summary>
        /// gets/sets the widget list contained by this HUDSystem which doesn't have any parent.
        /// Only the root elements are in this list. The ones that are displayed by the HUDSystem and not by their parents.
        /// </summary>
        public List<CWidget> DisplayList { get; set; }

        /// <summary>
        /// This represents the shader used by this HUDSystem for each widget.
        /// If you choose to change the shader, you will have to override the CHUDShader.Apply() method.
        /// </summary>
        public CHUDShader Shader { get { return m_shader; } set { m_shader = value; } }

        public CScene Scene { get { return m_scene; } set { m_scene = value; } }

        public CWidget WidgetClicked { get { return m_widgetLeftClicked; } set { m_widgetLeftClicked = value; } }

        public bool IsCursorVisible { get { return m_isCursorVisible; } set { m_isCursorVisible = value; } }

        public CCursor Cursor { get { return m_cursor; } set { m_cursor = value; } }

        public SpriteBatch SpriteBatch
        {
            get { return m_spriteBatch; }
            set { m_spriteBatch = value; }
        }

        /// <summary>
        /// Gets/sets the Skinner used for this HUDSystem.
        /// If the skin isn't already loaded, the skinner will load the default skin.
        /// </summary>
        public CSkinner Skinner
        {
            get { return m_skinner; }
            set
            {
                m_skinner = value;
                if (!m_skinner.IsLoaded)
                    m_skinner.LoadDefaultSkin();
            }
        }
        
        #endregion

        #region ctor

        private void ctor(string name)
        {
            m_name = name;
            this.Game = CDare.Instance.Game;
            this.m_spriteBatch = CDare.Instance.SpriteBatch;
            Widgets = new Dictionary<string, CWidget>();
            m_widgetLeftClicked = null;
            m_mouseLeftPressed = false;
            DisplayList = new List<CWidget>();
            m_shader = new CHUDShader();
            //m_pressedEvents = new Queue<CMouseButtonEvent>();
        }

        public CHUDSystem(string name, CSkinner skin)
        {
            ctor(name);
            m_skinner = skin;
            m_skinner.SpriteBatch = m_spriteBatch;
            m_cursor = new CCursor(this);
        }

        public CHUDSystem(string name)
            : this(name, (string)null)
        {

        }

        public CHUDSystem(string name, string skinFilePath)
        {
            ctor(name);
            m_skinner = new CSkinner();
            m_skinner.SpriteBatch = m_spriteBatch;
            if (skinFilePath == null)
                m_skinner.LoadDefaultSkin();
            else
                m_skinner.LoadSkin(skinFilePath);
            m_cursor = new CCursor(this);
        }

        #endregion

        #region GetElement

        /// <summary>
        /// Gets a widget contained by the HUDSystem.
        /// </summary>
        public CWidget GetElement(string name)
        {
            if (!Widgets.ContainsKey(name))
                return null;
            return Widgets[name];
        }

        public CWidget GetElement(Rectangle pos)
        {
            return GetElement(pos, (CWidget)null);
        }

        /// <summary>
        /// Gets the top level widget in a given rectangle.
        /// </summary>
        public CWidget GetElement(Rectangle pos, CWidget except)
        {
            CWidget ret = null;
            for (int i = Widgets.Count - 1; i >= 0; --i)
            {
                CWidget wid = Widgets.Values.ElementAt(i);
                if (wid.IsVisible && wid != except && wid.isOver(pos))
                {
                    ret = wid.GetElement(pos, except);
                    break;
                }
            }

            return ret;
        }

        /// <summary>
        /// Gets the top level widget in a given rectangle.
        /// </summary>
        public CWidget GetElement(Rectangle pos, Type except)
        {
            List<CWidget> wids = new List<CWidget>();
            CWidget ret = null;
            for (int i = Widgets.Count - 1; i >= 0; --i)
            {
                CWidget wid = Widgets.Values.ElementAt(i);
                if (wid.IsVisible && wid.GetType() != except && wid.isOver(pos))
                {
                    ret = wid.GetElement(pos, except);
                    break;
                }
            }

            return ret;
        }
        public List<CWidget> GetElements(Rectangle pos)
        {
            return GetElements(pos, (CWidget)null);
        }
        /// <summary>
        /// Gets the list of widgets with a rectangle that intersects the given rectangle.
        /// </summary>
        public List<CWidget> GetElements(Rectangle pos, CWidget except)
        {
            List<CWidget> wids = new List<CWidget>();
            for (int i = Widgets.Count - 1; i >= 0; --i)
            {
                CWidget wid = Widgets.Values.ElementAt(i);
                if (wid != except && wid.isOver(pos))
                    wids.Add(wid);
            }
            return wids;
        }

        /// <summary>
        /// Gets the list of widgets with a rectangle that intersects the given rectangle.
        /// </summary>
        public List<CWidget> GetElements(Rectangle pos, Type except)
        {
            List<CWidget> wids = new List<CWidget>();
            for (int i = Widgets.Count - 1; i >= 0; --i)
            {
                CWidget wid = Widgets.Values.ElementAt(i);
                if (wid.GetType() != except && wid.isOver(pos))
                    wids.Add(wid);
            }
                
            return wids;
        }


        /// <summary>
        /// Gets the list of widgets under the cursor.
        /// </summary>
        public bool IsUnderCursor(Type type)
        {
            for (int i = 0; i < DisplayList.Count; ++i)
            {
                CWidget wid = DisplayList[i];
                if (wid.IsActive && wid.IsVisible && wid.GetType() == type)
                    if (wid.isMouseOver(DARE.CDare.Instance.InputMgr.Mouse))
                        return true;
            }
            return false;
        }


        /// <summary>
        /// Gets the list of widgets under the cursor.
        /// </summary>
        public List<CWidget> GetElementsUnderCursor(CMouse mouse)
        {
            List<CWidget> wids = new List<CWidget>();
            for (int i = DisplayList.Count - 1; i >= 0; --i)
            {
                CWidget wid = DisplayList[i];
                if (wid.IsActive && wid.IsVisible && wid.isMouseOver(mouse))
                    wids.Add(wid);
            }
            return wids;
        }

        public CWidget GetElement(CMouse mouse)
        {
            return GetElement(mouse, (CWidget)null);
        }

        /// <summary>
        /// Gets the top level widget under the cursor.
        /// </summary>
        public CWidget GetElement(CMouse mouse, CWidget except)
        {
            CWidget ret = null;
            for (int i = Widgets.Count - 1; i >= 0; --i)
            {
                CWidget wid = Widgets.Values.ElementAt(i);
                if (wid.IsVisible && wid != except && wid.isMouseOver(mouse) && (except == null || !wid.IsInHierarchy(except)))
                {
                    ret = wid.GetElement(mouse, except);
                    break;
                }
            }
            return ret;
        }

        /// <summary>
        /// Gets the top level widget under the cursor.
        /// </summary>
        public CWidget GetElement(CMouse mouse, Type except)
        {
            CWidget ret = null;
            for (int i = Widgets.Count - 1; i >= 0; --i)
            {
                CWidget wid = Widgets.Values.ElementAt(i);
                if (wid.IsVisible && wid.GetType() != except && wid.isMouseOver(mouse) && !wid.IsInHierarchy(except))
                {
                    ret = wid.GetElement(mouse, except);
                    break;
                }
            }
            return ret;
        }

        /// <summary>
        /// Gets the root widget of a widget.
        /// </summary>
        public CWidget GetRootElement(string name)
        {
            CWidget widget = GetElement(name);
            while (widget.Parent != null)
                widget = widget.Parent;
            return widget;
        }

        /// <summary>
        /// Gets the root widget of a widget.
        /// </summary>
        public CWidget GetRootElement(CWidget wid)
        {
            CWidget widget = GetElement(wid.Name);
            while (widget.Parent != null)
                widget = widget.Parent;
            return widget;
        }

        #endregion

        #region add remove

        public void Add(CWidget widget)
        {
            Add(widget, false);
        }
        /// <summary>
        /// Add a widget to a given HUDSystem.
        /// If the widget was created with this HUDSystem in parameters, the widget is already added to this HUDSystem.
        /// </summary>
        public void Add(CWidget widget, bool force)
        {
            if (widget != null && widget.Name != "")
            {
                if (!Widgets.ContainsKey(widget.Name))
                {
                    Widgets.Add(widget.Name, widget);
                    widget.HUD = this;
                }
                else if (Widgets[widget.Name] != widget)
                {
                    if (force)
                        Widgets[widget.Name] = widget;
                    else
                        throw new Exception("A Widget with this name is already registered to this HUD System.");
                }
            }
            if (widget.Children != null)
                foreach (CWidget w in widget.Children.Values)
                    Add(w, force);
            if (widget.Parent == null && !DisplayList.Contains(widget))
                DisplayList.Add(widget);
        }

        /// <summary>
        /// Detachs the widget from the HUDSystem.
        /// The widget isn't destroyed. It is just removed from the DisplayedList list.
        /// If the widget has any parent, it will always be draw.
        /// </summary>
        public void Detach(CWidget widget)
        {
            if (widget != null && widget.Name != "")
            {
                //Widgets.Remove(widget.Name);
                DisplayList.Remove(widget);
            }
        }

        /// <summary>
        /// Detachs the widget from the HUDSystem.
        /// The widget isn't destroyed. It is just removed from the Widgets and DisplayedList lists.
        /// </summary>
        public void Detach(string name)
        {
            if (name != "" && Widgets.ContainsKey(name))
                Detach(Widgets[name]);
        }

        /// <summary>
        /// Removes a widget from the HUDSystem.
        /// The widget HUDSystem is cleared entirely.
        /// </summary>
        public void Remove(CWidget widget)
        {
            if (widget != null && widget.Name != "" && Widgets.ContainsKey(widget.Name))
            {
                widget.Clear();
                Widgets.Remove(widget.Name);
                DisplayList.Remove(widget);
                //widget.HUD = null;
            }
        }

        /// <summary>
        /// Removes a widget from the HUDSystem.
        /// The widget HUDSystem is set to null.
        /// </summary>
        public void Remove(string name)
        {
            if (name != "" && Widgets.ContainsKey(name))
                Remove(Widgets[name]);
        }

        #endregion

        #region focus

        /// <summary>
        /// Sets the given widget at top level
        /// </summary>
        public void SetFocusOn(CWidget widget)
        {
            CWidget wid = GetRootElement(widget);
            if (wid == null)
                return;
            DisplayList.Remove(wid);
            DisplayList.Add(wid);
        }

        /// <summary>
        /// Sets the given widget at top level
        /// </summary>
        public void SetFocusOn(string name)
        {
            CWidget wid = GetRootElement(name);
            if (wid == null)
                return;
            DisplayList.Remove(wid);
            DisplayList.Add(wid);
        }

        #endregion

        #region show hide

        public bool IsVisible { get { return m_isVisible; } }

        /// <summary>
        /// Sets the visibility to true
        /// </summary>
        public void Show()
        {
            m_isVisible = true;
        }

        /// <summary>
        /// Sets the visibility to false
        /// </summary>
        public void Hide()
        {
            m_isVisible = false;
        }

        #endregion

        #region widgetsActivation

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        public void SetActiveStateToAllDisplayedWidgets(bool value)
        {
            foreach (CWidget wid in DisplayList)
                wid.IsActive = value;
        }

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null with an unique exception.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        public void SetActiveStateToAllDisplayedWidgetsExcept(bool value, CWidget widget)
        {
            foreach (CWidget wid in DisplayList)
                if (!wid.Equals(widget))
                    wid.IsActive = value;
        }

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null with an unique exception.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        public void SetActiveStateToAllDisplayedWidgetsExcept(bool value, string name)
        {
            foreach (CWidget wid in DisplayList)
                if (wid.Name != name)
                    wid.IsActive = value;
        }

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null with an exception by an unique type.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        public void SetActiveStateToAllDisplayedWidgetsExcept(bool value, Type type)
        {
            foreach (CWidget wid in DisplayList)
                if (!wid.GetType().Equals(type))
                    wid.IsActive = value;
        }

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null with a list of exception.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        /// <param name="widget">the list of exceptions</param>
        public void SetActiveStateToAllDisplayedWidgetsExcept(bool value, List<CWidget> widget)
        {
            foreach (CWidget wid in DisplayList)
                if (!widget.Contains(wid))
                    wid.IsActive = value;
        }

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null with a list of exception.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        /// <param name="widget">the list of exceptions</param>
        public void SetActiveStateToAllDisplayedWidgetsExcept(bool value, List<string> names)
        {
            foreach (CWidget wid in DisplayList)
                if (!names.Contains(wid.Name))
                    wid.IsActive = value;
        }

        /// <summary>
        /// Activates/Unactivates every widgets with a parent set to null with a list of exception by type.
        /// </summary>
        /// <param name="value">the activation state to set</param>
        /// <param name="widget">the list of exceptions</param>
        public void SetActiveStateToAllDisplayedWidgetsExcept(bool value, List<Type> types)
        {
            foreach (CWidget wid in DisplayList)
                if (!types.Contains(wid.GetType()))
                    wid.IsActive = value;
        }

        #endregion

        #region update/draw

        //Queue<CMouseButtonEvent> m_pressedEvents;

        private bool ButtonReleased(CMouse mouse, ref int buttonEventIndex, ref CWidget widgetClicked, ref bool mousePressed)
        {
            bool eventHappend = false;
            if (widgetClicked != null && widgetClicked.IsActive)
            {
                
                widgetClicked.onRelease(mouse);
                if (widgetClicked.IsInputConsumer)
                {
                    eventHappend = true;
                    if (mouse.ButtonReleasedEvent.Contains(mouse.ButtonReleasedEvent[buttonEventIndex]))
                        mouse.ButtonReleasedEvent.Remove(mouse.ButtonReleasedEvent[buttonEventIndex]);
                    --buttonEventIndex;
                }
            }
            widgetClicked = null;
            mousePressed = false;
            return eventHappend;
        }

        private bool ButtonPressed(CMouse mouse, ref int buttonEventIndex, ref CWidget widgetClicked, ref bool mousePressed)
        {
            bool eventHappend = false;
            m_pressedButton = mouse.ButtonPressedEvent[buttonEventIndex];
            for (int j = DisplayList.Count - 1; j >= 0; --j)
            {
                CWidget wid = DisplayList[j];
                if (wid.IsVisible && wid.IsActive && wid.isMouseOver(mouse))
                {
                    wid.onClick(mouse);
                    widgetClicked = wid;
                    break;
                }
            }
            if (widgetClicked != null)
            {
                
                if (widgetClicked.IsClickable)
                {
                    DisplayList.Remove(widgetClicked);
                    DisplayList.Add(widgetClicked);
                }
                if (widgetClicked.IsInputConsumer)
                {
                    eventHappend = true;
                    if (mouse.ButtonPressedEvent.Contains(mouse.ButtonPressedEvent[buttonEventIndex]))
                        mouse.ButtonPressedEvent.Remove(mouse.ButtonPressedEvent[buttonEventIndex]);
                    --buttonEventIndex;
                }
                //m_pressedEvents.Enqueue(mouse.ButtonPressedEvent[i]);
            }
            mousePressed = true;
            return eventHappend;
        }

        public override void Update(GameTime gameTime)
        {
            if (m_isCursorVisible)
                m_cursor.Update(gameTime);
            if (!m_isVisible)
                return;
            for (int i = 0; i < DisplayList.Count; ++i)
                if (DisplayList.ElementAt(i).Parent != null)
                {
                    if (DisplayList.ElementAt(i).Parent.IsVisible)
                        DisplayList.ElementAt(i).Show();
                    else
                        DisplayList.ElementAt(i).Hide();
                    DisplayList.ElementAt(i).IsActive = DisplayList.ElementAt(i).Parent.IsActive;
                    DisplayList.RemoveAt(i);
                    --i;
                }
            bool eventHappend = false;
            //MouseState ms = Mouse.GetState();
            CMouse mouse = CDare.Instance.InputMgr.Mouse;


            for (int i = 0; i < mouse.ButtonReleasedEvent.Count; ++i)
            {
                if (mouse.ButtonReleasedEvent[i] == CMouse.EButton.LEFT)
                    eventHappend = ButtonReleased(mouse, ref i, ref m_widgetLeftClicked, ref m_mouseLeftPressed);
                else if (mouse.ButtonReleasedEvent[i] == CMouse.EButton.RIGHT)
                    eventHappend = ButtonReleased(mouse, ref i, ref m_widgetRightClicked, ref m_mouseRightPressed);
                else if (mouse.ButtonReleasedEvent[i] == CMouse.EButton.MIDDLE)
                    eventHappend = ButtonReleased(mouse, ref i, ref m_widgetMiddleClicked, ref m_mouseMiddlePressed);
            }

            for (int i = 0; i < mouse.ButtonPressedEvent.Count; ++i)
            {
                if (mouse.ButtonPressedEvent[i] == CMouse.EButton.LEFT)
                    eventHappend = ButtonPressed(mouse, ref i, ref m_widgetLeftClicked, ref m_mouseLeftPressed);
                else if (mouse.ButtonPressedEvent[i] == CMouse.EButton.RIGHT)
                    eventHappend = ButtonPressed(mouse, ref i, ref m_widgetRightClicked, ref m_mouseRightPressed);
                else if (mouse.ButtonPressedEvent[i] == CMouse.EButton.MIDDLE)
                    eventHappend = ButtonPressed(mouse, ref i, ref m_widgetMiddleClicked, ref m_mouseMiddlePressed);
            }

            
            if (eventHappend)
                mouse.Clear();

            for (int i = DisplayList.Count - 1; i >= 0; --i)
            {
                CWidget wid = DisplayList[i];
               
                if (!m_mouseLeftPressed)
                {
                    if (wid.IsVisible && wid.IsActive && !wid.WasMouseOver && wid.isMouseOver(mouse))
                    {
                        wid.onMouseOverEnter(mouse);
                        break;
                    }
                    if (wid.WasMouseOver && !wid.isMouseOver(mouse))
                        wid.onMouseOverExit(mouse);
                }
            }
           
            for (int i = DisplayList.Count - 1; i >= 0; --i)
            {
                CWidget wid = DisplayList[i];
                if (wid.IsVisible)
                    wid.Update(gameTime);
            }
        }

        public override void Draw(GameTime gameTime)
        {
            if (!m_isVisible)
            {
                if (m_isCursorVisible)
                {
                    m_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, m_shader);
                    m_cursor.Draw(gameTime);
                    m_spriteBatch.End();
                }
                return;
            }
            lock (m_spriteBatch)
            {
                m_spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, m_shader);
                for (int i = 0; i < DisplayList.Count; ++i)
                {
                    CWidget wid = DisplayList[i];
                    if (wid.IsVisible)
                        wid.Draw(gameTime);
                }
                m_spriteBatch.End();
                m_spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, m_shader);
                if (m_isCursorVisible)
                    m_cursor.Draw(gameTime);
                m_spriteBatch.End();
            }
        }

        #endregion

    };
}
